package cn.jiangjiesheng.bootstrap.filestore.impl;


import cn.jiangjiesheng.bootstrap.filestore.properties.FileStoreProperties;
import cn.jiangjiesheng.bootstrap.filestore.dto.ResultMap;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import org.apache.commons.lang3.time.DateUtils;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Objects;

/**
 * minio
 * <p>
 * Created by dev@jiangjiesheng.cn on 2021/7/9  <br/>
 */
@Slf4j
public class MinioFileStore extends AbstractFileStore<MinioClient> {

    public MinioFileStore(FileStoreProperties fileStoreProperties) {
        log.debug("Initializing minio client");
        this.setFileStoreProperties(fileStoreProperties);
        initializeClient(fileStoreProperties);
    }

    @Override
    public MinioClient initializeClient(FileStoreProperties properties) {
        MinioClient minioClient = MinioClient.builder()
                .endpoint(HttpUrl.parse(properties.getEndpoint()))
                .credentials(properties.getAk(), properties.getSk())
                .httpClient(new OkHttpClient())
                .build();
        setStoreClient(minioClient);
        return getStoreClient();
    }


    @Override
    public String putBigObject(String bucket, String objectId, String filepath) {
        return "";
    }

    @Override
    public String putBigObject(String objectId, String filepath) {
        return this.putBigObject(getFileStoreProperties().getDefaultBucket(), objectId, filepath);
    }

    @Override
    public void putObject(String bucket, String objectId, InputStream fileData) {
        try {
            getStoreClient().putObject(PutObjectArgs.builder().bucket(bucket)
                    .object(objectId)
                    .stream(fileData, -1, 5 * 1024 * 1024)
                    .build()
            );
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (InsufficientDataException e) {
            e.printStackTrace();
        } catch (InternalException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (XmlParserException e) {
            e.printStackTrace();
        }
    }


    @Override
    public InputStream getObject(String bucket, String objectId) {
        try {
            return getStoreClient().getObject(GetObjectArgs.builder().bucket(bucket).object(objectId).build());
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (InsufficientDataException e) {
            e.printStackTrace();
        } catch (InternalException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (XmlParserException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void publicRead(String bucket, String objectId, boolean enabled) {
        try {
            StringBuilder builder = new StringBuilder();
            builder.append("{\n");
            builder.append("    \"Statement\": [\n");
            builder.append("        {\n");
            builder.append("            \"Action\": \"s3:GetObject\",\n");
            builder.append("            \"Effect\": \"" + (enabled ? "Allow" : "Deny") + "\",\n");
            builder.append("            \"Principal\": \"*\",\n");
            builder.append("            \"Resource\": \"arn:aws:s3:::" + bucket + "/*\"\n");
            builder.append("        }\n");
            builder.append("    ],\n");
            builder.append("    \"Version\": \"2012-10-17\"\n");
            builder.append("}\n");
            getStoreClient().setBucketPolicy(SetBucketPolicyArgs
                    .builder()
                    .bucket(bucket)
                    .config(builder.toString())
                    .build());
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (InsufficientDataException e) {
            e.printStackTrace();
        } catch (InternalException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (XmlParserException e) {
            e.printStackTrace();
        }
    }


    @Override
    public void copyObject(String bucket, String objectId, String targetBucket, String targetObjectId) {
        try {
            getStoreClient().copyObject(CopyObjectArgs.builder()
                    .source(CopySource.builder().bucket(bucket).object(objectId).build())
                    .bucket(targetBucket)
                    .object(targetObjectId)
                    .build());
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (InsufficientDataException e) {
            e.printStackTrace();
        } catch (InternalException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (XmlParserException e) {
            e.printStackTrace();
        }
    }


    @Override
    public Boolean doesBucketExists(String bucket) {
        try {
            return getStoreClient().bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (InsufficientDataException e) {
            e.printStackTrace();
        } catch (InternalException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (XmlParserException e) {
            e.printStackTrace();
        }
        return null;
    }


    @Override
    public void createBucket(String bucket) {
        try {
            getStoreClient().makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (InsufficientDataException e) {
            e.printStackTrace();
        } catch (InternalException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (XmlParserException e) {
            e.printStackTrace();
        }
    }


    @Override
    public void deleteObject(String bucket, String objectId) {
        try {
            getStoreClient().removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectId).build());
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (InsufficientDataException e) {
            e.printStackTrace();
        } catch (InternalException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (XmlParserException e) {
            e.printStackTrace();
        }
    }


    @Override
    public ResultMap deleteMultipleObjects(String bucket, String... objectIds) {
        ResultMap resultMap = new ResultMap();
        for (String objectId : objectIds) {
            deleteObject(bucket, objectId);
            resultMap.put(objectId, true);
        }
        return resultMap;
    }


    @Override
    public ResultMap doesObjectsExists(String bucket, String... objectIds) {
        ResultMap resultMap = new ResultMap();
        for (String objectId : objectIds) {
            resultMap.put(objectId, doesObjectExists(bucket, objectId));
        }
        return resultMap;
    }


    @Override
    public Boolean doesObjectExists(String bucket, String objectId) {
        var meta = getObjectMeta(bucket, objectId);
        if (Objects.isNull(meta) || !meta.containsKey("deleteMarker")) {
            return false;
        }
        return getObjectMeta(bucket, objectId).get("deleteMarker").equals(false);
    }


    @Override
    public ResultMap getObjectMeta(String bucket, String objectId) {
        ResultMap resultMap = new ResultMap();
        try {
            StatObjectResponse statObjectResponse = getStoreClient()
                    .statObject(StatObjectArgs.builder()
                            .bucket(bucket)
                            .object(objectId)
                            .build()
                    );
            resultMap.put("contentType", statObjectResponse.contentType());
            resultMap.put("deleteMarker", statObjectResponse.deleteMarker());
            resultMap.put("lastModified", statObjectResponse.lastModified());
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (InsufficientDataException e) {
            e.printStackTrace();
        } catch (InternalException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (XmlParserException e) {
            e.printStackTrace();
        }
        return resultMap;
    }


    @Override
    public ResultMap generatePreSignedURL(String bucket, String... objectIds) {
        return generatePreSignedURL(bucket, DateUtils.addMinutes(new Date(), 60), objectIds);
    }


    @Override
    public ResultMap generatePreSignedURL(String bucket, Date expiration, String... objectIds) {
        ResultMap resultMap = new ResultMap();
        long expirationSeconds = expiration.getTime() - System.currentTimeMillis();
        try {
            for (String objectId : objectIds) {
                String url = getStoreClient().getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                        .bucket(bucket)
                        .method(Method.GET)
                        .object(objectId)
                        .expiry((int) expirationSeconds / 1000)
                        .build()
                );
                resultMap.put(objectId, url);
            }
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (InsufficientDataException e) {
            e.printStackTrace();
        } catch (InternalException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (XmlParserException e) {
            e.printStackTrace();
        } catch (ServerException e) {
            e.printStackTrace();
        }
        return resultMap;
    }

    @Override
    public void putObject(String objectId, InputStream fileData) {
        this.putObject(this.getFileStoreProperties().getDefaultBucket(), objectId, fileData);
    }

    @Override
    public InputStream getObject(String objectId) {
        return this.getObject(getFileStoreProperties().getDefaultBucket(), objectId);
    }

    @Override
    public void deleteObject(String objectId) {
        this.deleteObject(getFileStoreProperties().getDefaultBucket(), objectId);
    }

    @Override
    public ResultMap deleteMultipleObjects(String... objectIds) {
        return this.deleteMultipleObjects(getFileStoreProperties().getDefaultBucket(), objectIds);
    }

    @Override
    public ResultMap doesObjectsExists(String... objectIds) {
        return this.doesObjectsExists(getFileStoreProperties().getDefaultBucket(), objectIds);
    }

    @Override
    public Boolean doesObjectExists(String objectId) {
        return this.doesObjectExists(getFileStoreProperties().getDefaultBucket(), objectId);
    }

    @Override
    public ResultMap getObjectMeta(String objectId) {
        return this.getObjectMeta(getFileStoreProperties().getDefaultBucket(), objectId);
    }

    @Override
    public ResultMap generatePreSignedURL(String... objectIds) {
        return this.generatePreSignedURL(getFileStoreProperties().getDefaultBucket(), objectIds);
    }

    @Override
    public ResultMap generatePreSignedURL(Date expiration, String... objectIds) {
        return this.generatePreSignedURL(getFileStoreProperties().getDefaultBucket(), expiration, objectIds);
    }

    @Override
    public ResultMap generatePreSignedURL(String objectId) {
        return this.generatePreSignedURL(getFileStoreProperties().getDefaultBucket(), new String[]{objectId});
    }


    @Override
    public void close() {
        log.debug("It's needn't to close the connect for minio.");
    }
}
